[LWD] feat(desktop): renew analytics consent from policyVersion - #20263
Conversation
Web Tools Build Status
|
There was a problem hiding this comment.
Pull request overview
Migrates Ledger Wallet Desktop’s analytics-consent renewal logic away from legacy live-common helpers to the shared @features/flow-analytics-consent decision engine, aligning desktop behavior with the major/minor policyVersion semantics and shared tracking gate.
Changes:
- Switched desktop consent decision + phase resolution (dialog, tracking gate, QA screen) to
@features/flow-analytics-consentand the@domain/entity-analytics-consentconsent info type. - Updated consent persistence paths so full reconsent writes a new consent date, while privacy-only acknowledgement updates the policy version without changing the consent date.
- Updated unit/integration tests to cover major bump renewal vs minor bump privacy acknowledgement, and invalid remote
policyVersionbehavior.
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Adds workspace deps, but also includes broad unrelated lockfile churn (see comment). |
| apps/ledger-live-desktop/package.json | Adds @features/flow-analytics-consent and @domain/entity-analytics-consent dependencies. |
| .changeset/calm-desks-renew.md | Declares a Desktop minor bump for the consent renewal behavior change. |
| apps/ledger-live-desktop/src/renderer/reducers/settings.ts | Updates tracking gate to use shared consent decision; aligns consent info typing with domain entity. |
| apps/ledger-live-desktop/src/renderer/reducers/settings.test.ts | Reworks tracking-enabled tests for major/minor bump + invalid remote version cases. |
| apps/ledger-live-desktop/src/renderer/actions/settings.ts | Changes setAnalyticsConsentInfo to accept the full AnalyticsConsentInfo object. |
| apps/ledger-live-desktop/src/renderer/screens/settings/sections/Developer/AnalyticsConsentOptInDevTool/AnalyticsConsentOptInDevScreen.tsx | Updates QA consent screen to display the shared decision and simulate major-bump/renewal scenarios. |
| apps/ledger-live-desktop/src/mvvm/features/AnalyticsOptInPrompt/hooks/useCommonLogic.tsx | Persists consent info using the new shape and reads normalized policy version from shared params. |
| apps/ledger-live-desktop/src/mvvm/features/AnalyticsConsentDialog/types.ts | Updates consent phase types import to the shared package. |
| apps/ledger-live-desktop/src/mvvm/features/AnalyticsConsentDialog/hooks/useAnalyticsConsentDialogViewModel.ts | Uses shared decision hook + phase resolver; implements privacy-only ack persistence behavior. |
| apps/ledger-live-desktop/src/mvvm/features/AnalyticsConsentDialog/components/AnalyticsConsentDialogIllustration.tsx | Updates consent phase type import. |
| apps/ledger-live-desktop/src/mvvm/features/AnalyticsConsentDialog/components/AnalyticsConsentDialogFooterActions.tsx | Updates consent phase type import. |
| apps/ledger-live-desktop/src/mvvm/features/AnalyticsConsentDialog/components/AnalyticsConsentDialogCopyBlock.tsx | Updates consent phase type import. |
| apps/ledger-live-desktop/src/mvvm/features/AnalyticsConsentDialog/tests/useAnalyticsConsentDialogViewModel.test.ts | Updates VM tests for major/minor bump behavior and consentDate preservation on privacy ack. |
| apps/ledger-live-desktop/src/mvvm/features/AnalyticsConsentDialog/integrations/AnalyticsConsentDialog.portfolio.integration.test.tsx | Updates integration coverage for privacy-only acknowledgement + state persistence expectations. |
| version: 2.3.1(@ledgerhq/device-management-kit@1.7.1(rxjs@7.8.2)) | ||
| '@ledgerhq/crypto-icons': | ||
| specifier: 'catalog:' | ||
| version: 2.0.4(@ledgerhq/lumen-design-core@0.1.23)(@ledgerhq/lumen-ui-rnative@0.1.52(e185c177b115a51380796750601a5dcf))(react-native@0.81.6(patch_hash=fbb22f6ab81c7336a446f504c4aef2f23fc558838854399c868c5590e29ac9f1)(@babel/core@7.28.5)(@react-native-community/cli@18.0.1(@typescript/typescript6@6.0.2))(@types/react@19.0.14)(react@19.1.4))(react@19.1.4) | ||
| version: 2.0.4(@ledgerhq/lumen-design-core@0.1.23)(@ledgerhq/lumen-ui-rnative@0.1.52(4a7216fe4cd1a636a0648780ad0a82ce))(react-native@0.81.6(patch_hash=fbb22f6ab81c7336a446f504c4aef2f23fc558838854399c868c5590e29ac9f1)(@babel/core@7.28.5)(@react-native-community/cli@18.0.1(@typescript/typescript6@6.0.2))(@types/react@19.0.14)(react@19.1.4))(react@19.1.4) | ||
| '@ledgerhq/device-intent': | ||
| specifier: workspace:^ | ||
| version: link:../../libs/device-intent |
Rsdoctor Bundle Diff AnalysisFound 7 projects in monorepo, 7 projects with changes. 📊 Quick Summary
📋 Detailed Reports (Click to expand)📁 desktop-mainPath:
📁 desktop-preloaderPath:
📁 desktop-rendererPath:
📁 desktop-webviewDappPreloaderPath:
📁 desktop-webviewPreloaderPath:
📁 desktop-workersPath:
📁 mobilePath:
Generated by Rsdoctor GitHub Action |
5e5b0d8 to
c9f36d2
Compare
❌ Action Required: Monitored Files ChangedThe following files in monitored folders have been modified:
Action Required: Please rebase your branch against git rebase origin/develop |
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 15 changed files in this pull request and generated no new comments.
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (2)
apps/ledger-live-desktop/src/mvvm/features/AnalyticsConsentDialog/hooks/useAnalyticsConsentDialogViewModel.ts:50
policyVersioncan fall back to the storedprivacyPolicyVersion, which may still be a legacy number (e.g.1). Persisting/tracking that value keeps it unnormalized, even though the new flow expects normalized"<major>.<minor>"versions (and tests now assert"1.0"). Consider normalizing the numeric fallback before using it.
const policyVersion = currentPolicyVersion?.normalized ?? consentInfo.privacyPolicyVersion;
apps/ledger-live-desktop/src/mvvm/features/AnalyticsOptInPrompt/hooks/useCommonLogic.tsx:37
policyVersionfalls back toconsentInfo.privacyPolicyVersion, which may still be a legacy number (e.g.1). That would be persisted as-is on submit, leaving the stored policy version unnormalized. Normalizing numeric fallbacks here keeps persisted values consistent with the new"<major>.<minor>"policyVersion format.
const policyVersion = currentPolicyVersion?.normalized ?? consentInfo.privacyPolicyVersion;
c9f36d2 to
bf73105
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 15 changed files in this pull request and generated no new comments.
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (1)
apps/ledger-live-desktop/src/renderer/screens/settings/sections/Developer/AnalyticsConsentOptInDevTool/AnalyticsConsentOptInDevScreen.tsx:160
- The "Stored privacy policy version" status is currently derived from
decision.kind !== "none", which also turns the stored-version line red for renewals caused only by an invalid/missing consent date. That makes the QA screen misleading because the stored policy version may still be up to date when renewal is purely date-driven.
Consider flagging an error state here only when the decision is privacy-related, or when renewal is specifically caused by stored version issues / major bump.
className={`min-w-0 flex-1 body-2 font-medium leading-relaxed ${
decision.kind !== "none" ? "text-error" : "text-success"
}`}
Co-authored-by: Cursor <cursoragent@cursor.com>
bf73105 to
4e6a0ec
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 15 changed files in this pull request and generated no new comments.
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (2)
apps/ledger-live-desktop/src/mvvm/features/AnalyticsOptInPrompt/hooks/useCommonLogic.tsx:38
policyVersioncan fall back toconsentInfo.privacyPolicyVersion, which may be a legacy number (e.g.1) or a non-normalized string (e.g."1"). That contradicts the PR’s intent of persisting a normalized"<major>.<minor>"version and can lead to mixed formats in persisted settings and analytics payloads.
Consider normalizing legacy values before persisting/tracking (at least normalize pure-integer values to "<n>.0", and keep invalid strings as-is).
const { currentPolicyVersion } = resolveAnalyticsOptInParams(analyticsOptInFlag);
const policyVersion = currentPolicyVersion?.normalized ?? consentInfo.privacyPolicyVersion;
const shouldWeTrack = isTrackingEnabled || !hasSeenAnalyticsOptInPrompt;
apps/ledger-live-desktop/src/mvvm/features/AnalyticsConsentDialog/hooks/useAnalyticsConsentDialogViewModel.ts:51
policyVersionfalls back to the storedprivacyPolicyVersion, which can still be a legacy number (e.g.1) or non-normalized string (e.g."1"). If that value is reused in tracking payloads or persisted on future updates, it can reintroduce mixed policy-version formats even though the shared flow normalizes to"<major>.<minor>".
Normalizing pure-integer values here keeps the persisted/telemetry format consistent without changing behavior for invalid stored versions.
const { isFeatureEnabled, decision, currentPolicyVersion } =
useAnalyticsConsentDecision(consentInfo);
// An invalid remote version must not erase the version the user already acknowledged.
const policyVersion = currentPolicyVersion?.normalized ?? consentInfo.privacyPolicyVersion;
|


✅ Checklist
npx changesetwas attached.📝 Description
Desktop still decided consent renewal with live-common helpers: a rolling
consentValidityDayswindow plus a flat numeric privacy version compare. That missed the ADR split where a majorpolicyVersionbump forces full analytics reconsent and a minor bump only asks for privacy acknowledgement.This PR points Ledger Wallet Desktop at
@features/flow-analytics-consentthe same way mobile already does. The dialog, tracking gate, settings persist path, and QA screen all use that shared verdict. Fresh choices (accept / decline / confirm preferences) write a new consent date and the normalized policy version. Privacy-only acknowledgement updates the version and leaves the consent date alone. Stale analytics consent (decision.kind === "renewal") turns optional tracking off until the user chooses again. Invalid remotepolicyVersiondoes not invent a prompt from version checks.consentValidityDaysstays on the feature flag for now and is no longer read by the desktop decision path. A follow-up stack layer will drop that field and prune leftover live-common / expiry paths.Stack layer 7 on
#20212(debug tip).❓ Context
🧐 Checklist for the PR Reviewers
Made with Cursor